﻿//#define ALL_FIELDS

using System;
using System.Linq;
using System.Windows;
using System.Collections.Generic;
using System.Windows.Controls;
using System.Windows.Shapes;

namespace Sonce.Editor
{
    // http://en.wikipedia.org/wiki/Unified_Modeling_Language
    // http://sourceforge.net/search/?type_of_search=soft&words=.net+uml

    /// <summary>
    /// Abstract logic that is hidden beyond the visual appearence.
    /// </summary>
    public partial class LogicManager
    {
        #region Unused
        //public LogicNode FindFirstConnection(LogicElement element1, LogicElement element2, double tolerance)
        //{
        //    LogicNode node = null;
        //    if (element1 != null && element2 != null && element1.HasNodes && element2.HasNodes)
        //    {
        //        foreach (Point point1 in element1.NodePositions)
        //        {
        //            foreach (Point point2 in element2.NodePositions)
        //            {
        //                IsConnected(point1, point2, tolerance);
        //            }
        //        }
        //    }
        //    return node;
        //}

        //Under construction
        //Find elements that intersect with the current element
        public static void FindOverlappedElements(ref Panel panel, FrameworkElement element, ref List<FrameworkElement> elements)
        {
            if (elements == null)
            {
                elements = new List<FrameworkElement>();
            }

            Point position = LayoutManager.GetAbsolutePosition(element);
            Rect elementBounds = new Rect(position, new Size(element.ActualWidth, element.ActualHeight));

            foreach (FrameworkElement visual in panel.Children)
            {
                Point visualPosition = new Point();
                if (LayoutManager.GetAbsolutePosition(element, ref position))
                {
                    Rect visualBounds = new Rect(visualPosition, new Size(visual.Width, visual.Height));
                    if (Helper.Intersect(elementBounds, visualBounds))
                    {
                        elements.Add(visual);
                        continue; //Can go into region/group?
                    }
                }

                if (visual is Panel)
                {
                    Panel container = visual as Panel;
                    FindOverlappedElements(ref container, element, ref elements); //Recursion
                }
            }
        }

        public static void FindOverlappedNodes()
        {
        }
        #endregion

        #region Private methods
        /// <summary>
        /// Find absolute positions of nodes recursively.
        /// </summary>
        /// <param name="element">[in] FrameworkElement, top-most element.</param>
        /// <param name="positions">[ref] List of found node positions.</param>
        private static void findNodePositions(FrameworkElement element, ref List<Point> positions)
        {
            if (positions == null)
            {
                positions = new List<Point>();
            }
            if (element != null && element is Panel)
            {
                Panel panel = element as Panel;
                foreach (FrameworkElement item in panel.Children)
                {
                    if (IsNode(item))
                    {
                        //Add node position
                        Point point = LayoutManager.GetAbsolutePosition(item);
                        positions.Add(point);
                    }
                    else
                    {
                        //Warning: recursion
                        findNodePositions(item, ref positions);
                    }
                }
            }
        }

        /// <summary>
        /// Find absolute positions of nodes recursively.
        /// </summary>
        /// <param name="element">[in] FrameworkElement, top-most element.</param>
        /// <param name="positions">[ref] List of found node positions.</param>
        private static void findNodes(FrameworkElement element, ref List<FrameworkElement> positions)
        {
            if (positions == null)
            {
                positions = new List<FrameworkElement>();
            }
            if (element != null && element is Panel)
            {
                Panel panel = element as Panel;
                foreach (FrameworkElement item in panel.Children)
                {
                    if (IsNode(item))
                    {
                        //Add node position
                        positions.Add(item);
                    }
                    else
                    {
                        //Warning: recursion
                        findNodes(item, ref positions);
                    }
                }
            }
        }

        //Works: find ElementTag, NodeTag, etc.
        private static void findObjects<T>(Panel panel, ref List<FrameworkElement> elements) where T : ITag
        {
            if (elements == null)
            {
                elements = new List<FrameworkElement>();
            }
            foreach (FrameworkElement visual in panel.Children)
            {
                if (visual.Tag != null && visual.Tag is T)
                {
                    T tag = (T)visual.Tag;
                    //tag.UniqueId;
                    elements.Add(visual);
                }
                else if (visual is Panel)
                {
                    //Warning: recursion
                    Panel container = visual as Panel;
                    findObjects<T>(container, ref elements);
                }
            }
        }
        #endregion

        #region Static methods
        /// <summary>
        /// Does the specified element match the node definition.
        /// </summary>
        /// <param name="element">FrameworkElement.</param>
        /// <returns>Returns true if the specified element is node.</returns>
        /// <remarks>Node is defined with an element with width = 0, height = 0 and visiblity hidden or collapsed.</remarks>
        public static bool IsNode(FrameworkElement element)
        {
            bool result = false;
            if (element != null)
            {
                //Node is defined with an element with width = 0, height = 0 and visiblity hidden
                result = (element.Tag is NodeTag) || (element.Width == 0 && element.Height == 0 && element.Visibility != Visibility.Visible);
            }
            return result;
        }

        /// <summary>
        /// Are the specified points overlapped (with some tolerance)?
        /// </summary>
        /// <param name="point1">First point.</param>
        /// <param name="point2">Second point.</param>
        /// <param name="tolerance">Tolerance in pixels.</param>
        /// <returns>Returns true if points are at the same position.</returns>
        public static bool IsOverlapped(Point p1, Point p2, double tolerance)
        {
            return (Math.Abs(p1.X - p2.X) < tolerance && Math.Abs(p1.Y - p2.Y) < tolerance);
        }

        /// <summary>
        /// Overloaded. Find absolute positions of nodes recursively.
        /// </summary>
        /// <param name="element">FrameworkElement, top-most element.</param>
        /// <returns>Returns list of found node positions.</returns>
        public static List<Point> FindNodePositions(FrameworkElement element)
        {
            List<Point> positions = new List<Point>();
            findNodePositions(element, ref positions);
            return positions;
        }

        /// <summary>
        /// Overloaded. Find absolute positions of nodes recursively.
        /// </summary>
        /// <param name="element">FrameworkElement, top-most element.</param>
        /// <returns>Returns list of found node positions.</returns>
        public static List<FrameworkElement> FindNodes(FrameworkElement element)
        {
            List<FrameworkElement> positions = new List<FrameworkElement>();
            findNodes(element, ref positions);
            return positions;
        }

        /// <summary>
        /// Find all object in panel recursivelly. Object must have Tag as T.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="panel"></param>
        /// <returns></returns>
        public static List<FrameworkElement> FindObjects<T>(Panel panel) where T : ITag
        {
            List<FrameworkElement> objects = new List<FrameworkElement>();
            findObjects<T>(panel, ref objects);
            return objects;
        }

        //Under construction
        public static Matrix<Guid> FindMatrix(Panel panel)
        {
            //Index starts from 1
            Matrix<Guid> matrix = new Matrix<Guid>(100, false, true);

            //Elements with nodes
            List<FrameworkElement> elements = FindObjects<ElementTag>(panel);

            //Free nodes (not bound to element)
            List<FrameworkElement> nodes = FindObjects<NodeTag>(panel);

            //Combine elements and nodes into nodes
            foreach (FrameworkElement element in elements)
            {
                List<FrameworkElement> elementNodes = FindNodes(element);
                nodes.AddRange(elementNodes);
            }

            //Now nodes contain all nodes (free and from elements)

            //Find lines, connections
            List<FrameworkElement> links = FindObjects<LinkTag>(panel);

            //Radial tolerance in pixels (two nodes must overlap within tolerance to be connected)
            int tolerance = 2;

            #region Find linked nodes
            foreach (FrameworkElement link in links)
            {
                LinkTag linkTag = link.Tag as LinkTag;
                List<Point> linkPoints = Helper.GetPathPoints(link); //Get all points of polyline

                //Check if node is on a link
                foreach (FrameworkElement node in nodes)
                {
                    NodeTag nodeTag = node.Tag as NodeTag;
                    if (nodeTag == null)
                    {
                        nodeTag = new NodeTag();
                        nodeTag.Position = LayoutManager.GetAbsolutePosition(node);
                    }

                    if (Helper.IsPointOnLine(linkPoints.ToArray(), nodeTag.Position, tolerance)) //TODO: Tolerance should be SnapToGrid value
                    {
                        //Node somewhere on a line
                        linkTag.Nodes.Add(node);
                    }
                }
            }
            #endregion

            #region Find logic nodes
            //TODO: Get connected lines

            //Connected graphical nodes on a line work like one large node (i.e. LogicNode)
            List<LogicNode> largeNodes = new List<LogicNode>();

            int index = 1;
            foreach (FrameworkElement link in links)
            {
                LinkTag linkTag = link.Tag as LinkTag;
                linkTag.MatrixIndex = index++;
                //largeNodes.Add(new LogicNode(linkTag.Nodes));
            }
            #endregion

            //Works only for elements with 2 nodes
            foreach (FrameworkElement element in elements)
            {
                List<int> indices = new List<int>();
                List<FrameworkElement> elementNodes = FindNodes(element);
                foreach (FrameworkElement elementNode in elementNodes)
                {
                    #region Find node index in matrix
                    foreach (FrameworkElement link in links)
                    {
                        LinkTag linkTag = link.Tag as LinkTag;
                        if (linkTag.Nodes.Contains(elementNode) && linkTag.MatrixIndex >= 1)
                        {
                            indices.Add(linkTag.MatrixIndex);
                            break;
                        }
                    }
                    #endregion
                }

                if (indices.Count == 2)
                {
                    int i = indices[0];
                    int j = indices[1];
                    Guid elementId = ((ElementTag)element.Tag).UniqueId;
                    matrix[i][j] = elementId;
                    matrix[j][i] = elementId;
                }
            }

            //TODO: Put logic nodes into matrix - row, column for node id; field for element id
            //TODO: Use BFS algorithm to do this

            return matrix;
        }

        /// <summary>
        /// Prints out all the children of graph fragment <paramref name="n" /> to Console in breadth-first order.
        /// </summary>
        /// <remarks>
        /// Reference: http://en.wikipedia.org/wiki/Breadth-first_search
        /// </remarks>
        public Queue<LogicNode> BreadthFirstSearch(LogicNode n)
        {
            Queue<LogicNode> q = new Queue<LogicNode>();
            q.Enqueue(n);
            while (q.Count > 0)
            {
                LogicNode m = q.Dequeue();
                //Console.WriteLine(m + ", ");
                //OnFound(m)
                foreach (LogicNode child in m.Children)
                {
                    q.Enqueue(child);
                }
            }
            return q;
        }
        #endregion

        #region Import/Export
        public void Import()
        {
            //TODO: Import from xml or Layer
        }

        public void Export()
        {
            //TODO: Export to xml
        }
        #endregion

        #region Generator
        public static LogicManager FromXaml(string xaml)
        {
            LogicManager logicTree = null;
            FrameworkElement element = Helper.LoadXaml(xaml);
            if (element != null && element is Panel)
            {
                logicTree = LogicManager.FromPanel((Panel)element);
            }
            return logicTree;
        }

        public static LogicManager FromPanel(Panel panel)
        {
            LogicManager logicTree = null;
            if (panel != null && panel.Children.Count > 0)
            {
                List<FrameworkElement> elements = LogicManager.FindObjects<ElementTag>(panel);

                //logicTree = new LogicManager();
                //foreach (FrameworkElement element in elements)
                //{
                //    logicTree.Add(element);
                //}
            }
            return logicTree;
        }
        #endregion
    }

    public partial class LogicNode
    {
        #region Constructors
        /// <summary>
        /// Class constructor.
        /// </summary>
        public LogicNode()
        {
        }
        #endregion

        public LogicNode(List<FrameworkElement> graphicalNodes)
        {
            //TODO: Connected graphical nodes on a line work like one large node (i.e. LogicNode)
        }

        public int NodeID;

        public List<int> ConnectedNodes;

        /// <summary>
        /// Get neighbour nodes.
        /// </summary>
        public List<LogicNode> Children
        {
            get
            {
                List<LogicNode> list = new List<LogicNode>();
                return list;
            }
        }
    }

    public partial class LogicElement
    {
        #region Members
        private Guid uniqueID = Guid.NewGuid();
        private FrameworkElement visual = null;
        #endregion

        #region Constructors
        /// <summary>
        /// Class constructor.
        /// </summary>
        public LogicElement()
        {
        }

        /// <summary>
        /// Class constructor.
        /// </summary>
        /// <param name="visual">Visual representation of the logical object.</param>
        public LogicElement(FrameworkElement visual)
        {
            this.Visual = visual;
        }
        #endregion

        #region Properties
        /// <summary>
        /// Get the unique ID of the element.
        /// </summary>
        public Guid UniqueID
        {
            get
            {
                return this.uniqueID;
            }
        }

        /// <summary>
        /// Get or set the visual representation of the logical object.
        /// </summary>
        public FrameworkElement Visual
        {
            get
            {
                return this.visual;
            }
            set
            {
                this.Visual = value;
                this.NodePositions = LogicManager.FindNodePositions(value);
            }
        }

        /// <summary>
        /// Get the node positions.
        /// </summary>
        public List<Point> NodePositions
        {
            get;
            private set;
        }

        /// <summary>
        /// Check if the element has any logical node.
        /// </summary>
        public bool HasNodes
        {
            get
            {
                return (this.NodePositions != null && NodePositions.Count > 0);
            }
        }
        #endregion
    }
}
